package com.xml.dom.ctl;

import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class BookCtlImp implements BookCtl {

	private Document doc;
	private static BookCtl ctl;
	private static final String FILE = "book3.xml";

	private BookCtlImp() {
		try {
			doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
					.parse(new File(FILE));
		} catch (Exception e) {
			this.doException(e);
		}
	}

	public static BookCtl getInterface() {
		if (null == ctl)
			ctl = new BookCtlImp();
		return ctl;
	}

	@Override
	public List<Map<String, String>> getAllBooks() {
		List<Map<String, String>> list = null;
		NodeList books = this.getBooks();
		if(null != books && books.getLength() > 0) {
			list = new ArrayList<Map<String,String>>();
			for(int i=0; i<books.getLength(); i++) {
				list.add(this.getMapFromElement((Element)books.item(i)));
			}
		}
		return list;
	}

	@Override
	public List<Map<String, String> > getBooksByTag(String name, String value) {
		List<Map<String, String>> books = null;
		NodeList list = ((Element) doc.getFirstChild())
				.getElementsByTagName(name);
		if (null != list && list.getLength() > 0) {
			for (int i = 0; i < list.getLength(); i++) {
				Node node = list.item(i);
				if (value.equals(node.getTextContent())) {
					if (null == books)
						books = new ArrayList<Map<String, String>> ();
					books.add(this.getMapFromElement((Element) node.getParentNode()));
				}
			}
		}
		return books;
	}
	
	@Override
	public Map<String, String> getBookDataByID(String id) {
		return this.getMapFromElement(this.getElementByID(id));
	}

	@Override
	public void add(Map<String, String> map) {
		// book element အသစ်တစ်ခုကို လုပ်ယူခြင်း
		Element book = doc.createElement("book");
		book.setAttribute("id", this.getMaxId());
		
		// key တန်ဖိုး Iterator ကို ရယူခြင်း
		Iterator<String> keys = map.keySet().iterator();
		
		// Iterator အတွင်း နောက် key တန်ဖိုး ရှိသ၍
		while (keys.hasNext()) {
			// နောက် key တန်ဖိုးကို ထုတ်ယူခြင်း
			String key = keys.next();
			// key အမည်ဖြင့် element တစ်ခုကို လုပ်ယူခြင်း
			Element ele = doc.createElement(key);
			// key ၏ တန်ဖိုးကို key element ၏ text တန်ဖိုးအဖြစ် 
			// သတ်မှတ်ခြင်း
			ele.setTextContent(map.get(key));
			// book တွင် key ကို ဖြည့်စွက်ခြင်း
			book.appendChild(ele);
		}
		
		// root element တွင် လုပ်ယူထားသော book elementကို 
		// ဖြည့်စွက်ခြင်း
		doc.getFirstChild().appendChild(book);
		// ပြုပြင်ပြီးသော XML ဖိုင်အား သိမ်းဆည်းခြင်း
		this.save();
	}

	@Override
	public void delete(String id) {
		// ဖျက်မည့်စာအုပ်
		Node delNode = this.getElementByID(id);
			
		// အကယ်၍ ဖျက်မည့်စာအုပ်ဟာ null မဟုတ်ခဲ့ရင်
		if(null != delNode) {
			// doc မှ စာအုပ်ကို ထုတ်ပစ်ပါမည်
			doc.getFirstChild().removeChild(delNode);
			this.save();
		} else {
			throw new BookCtlException("ဖျက်ရန် စာအုပ်ကို ရှာမတွေ့ပါ။");
		}
	}

	@Override
	public void edit(String id, Map<String, String> map) {
		// အိုင်ဒီဖြင့် စာအုပ်အဲလီမန့်ကိုရယူခြင်း
		Element book = this.getElementByID(id);
		
		if(null != book) {
			Iterator<String> keys = map.keySet().iterator();
			
			while(keys.hasNext()) {
				// ပြုပြင်လိုသည့် key ကို ရယူခြင်း
				String key = keys.next();
				// ရရှိလာသော key ဖြင့် Element ကို ရယူခြင်း
				Element ele = (Element) book.getElementsByTagName(key).item(0);
				// Element ၏ တန်ဖိုးကို ပြုပြင်ခြင်း
				ele.setTextContent(map.get(key));
			}
			// XML ဖိုင်အား သိမ်းဆည်းခြင်း
			this.save();
		} else {
			// စာအုပ်ကို ရှာမတွေ့ခဲ့ပါက
			throw new BookCtlException("ပြင်ဆင်ရန် စာအုပ်ကိုရှာမတွေ့ပါ။");
		}
		
	}

	private Map<String, String> getMapFromElement(Element book) {
		Map<String, String> map = null;
		if (null != book) {
			// ရလဒ်ကိုပုံစံချခြင်း
			map = new HashMap<String, String>();
			// စာအုပ်အဲလီမန့်၏ သားအဆင့်နုတ်များအားရယူခြင်း
			NodeList nodes = book.getChildNodes();
			// နုတ်တစ်ခုချင်း အလုပ်လုပ်စေခြင်း
			for(int i=0; i<nodes.getLength(); i++) {
				Node node = nodes.item(i);
				// i နံပါတ်မြောက် နုတ်သည် Element ၏ instance ဖြစ်ခဲ့ပါက
				if (node instanceof Element) {
					Element e = (Element)node;
					map.put(e.getTagName(), e.getTextContent());
				}
			}
		}
		return map;
	}

	/**
	 * အိုင်ဒီကိုပေး၍ စာအုပ် အဲလီးမန့်ကို ရယူခြင်း
	 * @param id
	 * @return
	 */
	private Element getElementByID(String id) {
		// စာအုပ်များ
		NodeList list = this.getBooks();

		if (null != list && list.getLength() > 0) {
			// ဖျက်မည့်စာအုပ် နုတ်ကိုရှာဖွေခြင်း
			for (int i = 0; i < list.getLength(); i++) {
				// ရယူလာတဲ့ id နဲ့ စာအုပ်၏ id ဟာ စာလုံးခြင်း တူညီခဲ့လျှင်
				if (id.equals(((Element) list.item(i)).getAttribute("id"))) {
					// နံပါတ် i မြောက်မှ နုတ်ကို Element အဖြစ်ပြန်ပို့ပါမည်
					return (Element)list.item(i);
				}
			}
		}
		return null;
	}

	private NodeList getBooks() {
		return ((Element) doc.getFirstChild()).getElementsByTagName("book");
	}

	private String getMaxId() {
		// Book များကို ရယူခြင်း
		NodeList list = getBooks();
		// စာအုပ်များသည် null မဟုတ်ပဲ၊ 0 ထက်ကြီးပါက
		if (null != list && list.getLength() > 0) {
			// နောက်ဆုံး စာအုပ်၏ ID ကို ရယူခြင်း
			String strId = ((Element) list.item(list.getLength() - 1))
					.getAttribute("id");
			// နောက်ဆုံး ID ကို တစ်ပေါင်းခြင်း
			int id = Integer.parseInt(strId) + 1;
			// id ကိန်း၏ စာကြောင်းတန်ဖိုးကို ရလဒ်အဖြစ်ပြန်ပေးခြင်း 
			return String.valueOf(id);
		}
		// 0 ကို ရလဒ်အဖြစ် ပြန်ပေးခြင်း
		return "0";
	}

	private void save() {
		try {
			TransformerFactory
					.newInstance()
					.newTransformer()
					.transform(new DOMSource(doc),
							new StreamResult(new FileWriter(FILE)));
		} catch (Exception e) {
			this.doException(e);
		}
	}

	private void doException(Exception e) {
		throw new BookCtlException(e.getClass().getSimpleName()
				+ "ကြောင့်ဖြစ်ပေါ်သော အမှားဖြစ်ပါသည်။");
	}

	
}
